package uk.ac.ox.zoo.seeg.abraid.mp.common.dto;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import uk.ac.ox.zoo.seeg.abraid.mp.common.dto.json.WrappedList;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
/**
* A spring message converter for producing CSV text from DTO objects.
* Copyright (c) 2014 University of Oxford
*/
public class CSVMessageConverter
extends AbstractHttpMessageConverter<WrappedList<?>> {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private static final String CANNOT_WRITE_NULL = "Cannot write collection containing null";
private static final String CANNOT_WRITE_MIXED_TYPES = "Cannot write collection containing mixed types";
private final CsvMapper csvMapper;
public CSVMessageConverter(CsvMapper csvMapper) {
super(
new MediaType("application", "csv", DEFAULT_CHARSET),
new MediaType("application", "*+csv", DEFAULT_CHARSET));
this.csvMapper = csvMapper;
}
/**
* Checks if the given class is supported for reading, and if the given media type is supported.
* @param clazz the class to test for support
* @param mediaType the media type to test for support
* @return {@code true} if supported; {@code false} otherwise
*/
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
// Don't support any csv parsing
return false;
}
/**
* Checks if the given class is supported for writing, and if the given media type is supported.
* @param clazz the class to test for support
* @param mediaType the media type to test for support
* @return {@code true} if supported; {@code false} otherwise
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return WrappedList.class.isAssignableFrom(clazz) && canWrite(mediaType);
}
/**
* Indicates whether the given class is supported by this converter.
* @param clazz the class to test for support
* @return {@code true} if supported; {@code false} otherwise
*/
@Override
protected boolean supports(Class<?> clazz) {
// Should not be called, since we override canRead/canWrite instead
throw new UnsupportedOperationException();
}
/**
* Implements the abstract template method that reads the actual object. Invoked from {@link #read}.
* @param clazz the type of object to return
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
@Override
protected WrappedList<?> readInternal(Class<? extends WrappedList<?>> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// Should not be called, since we return false for all canRead
throw new UnsupportedOperationException();
}
/**
* Implements the abstract template method that writes the actual body. Invoked from {@link #write}.
* @param t the object to write to the output message
* @param outputMessage the HTTP output message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
@Override
protected void writeInternal(WrappedList<?> t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
Iterator<?> it = t.getList().iterator();
Class<?> listType = null;
while (it.hasNext()) {
Object entry = it.next();
if (entry == null) {
throw new HttpMessageNotWritableException(CANNOT_WRITE_NULL);
}
Class<?> entryType = entry.getClass();
if (listType == null) {
listType = entryType;
} else if (!entryType.equals(listType)) {
throw new HttpMessageNotWritableException(CANNOT_WRITE_MIXED_TYPES);
}
}
if (listType != null) {
CsvSchema schema = csvMapper.schemaFor(listType).withHeader();
csvMapper.writer(schema).writeValue(outputMessage.getBody(), t.getList());
}
}
}